from pymodbus.interfaces import IModbusDecoder

from pymodbus.pdu import IllegalFunctionRequest
from pymodbus.pdu import ExceptionResponse
from pymodbus.pdu import ModbusRequest, ModbusResponse
from pymodbus.pdu import ModbusExceptions as ecode
from pymodbus.exceptions import ModbusException, MessageRegisterException
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.diag_message import *
from pymodbus.file_message import *
from pymodbus.other_message import *
from pymodbus.mei_message import *
from pymodbus.register_read_message import *
from pymodbus.register_write_message import *
from pymodbus.compat import byte2int

import logging
_logger = logging.getLogger(__name__)


class DcsServerDecoder(IModbusDecoder):
    """ Request Message Factory (Server)

    To add more implemented functions, simply add them to the list
    """
    __function_table = [
        ReadHoldingRegistersRequest,
        ReadDiscreteInputsRequest,
        ReadInputRegistersRequest,
        ReadCoilsRequest,
        WriteMultipleCoilsRequest,
        WriteMultipleRegistersRequest,
        WriteSingleRegisterRequest,
        WriteSingleCoilRequest,
        ReadWriteMultipleRegistersRequest,
        DiagnosticStatusRequest,
        ReadExceptionStatusRequest,
        GetCommEventCounterRequest,
        GetCommEventLogRequest,
        ReportSlaveIdRequest,
        ReadFileRecordRequest,
        WriteFileRecordRequest,
        MaskWriteRegisterRequest,
        ReadFifoQueueRequest,
        ReadDeviceInformationRequest,
    ]
    __sub_function_table = [
        ReturnQueryDataRequest,
        RestartCommunicationsOptionRequest,
        ReturnDiagnosticRegisterRequest,
        ChangeAsciiInputDelimiterRequest,
        ForceListenOnlyModeRequest,
        ClearCountersRequest,
        ReturnBusMessageCountRequest,
        ReturnBusCommunicationErrorCountRequest,
        ReturnBusExceptionErrorCountRequest,
        ReturnSlaveMessageCountRequest,
        ReturnSlaveNoResponseCountRequest,
        ReturnSlaveNAKCountRequest,
        ReturnSlaveBusyCountRequest,
        ReturnSlaveBusCharacterOverrunCountRequest,
        ReturnIopOverrunCountRequest,
        ClearOverrunCountRequest,
        GetClearModbusPlusRequest,
        ReadDeviceInformationRequest,
    ]

    def __init__(self):
        """ Initializes the client lookup tables
        """
        functions = set(f.function_code for f in self.__function_table)
        self.__lookup = dict([(f.function_code, f) for f in self.__function_table])
        self.__sub_lookup = dict((f, {}) for f in functions)
        for f in self.__sub_function_table:
            self.__sub_lookup[f.function_code][f.sub_function_code] = f

    def decode(self, message):
        """ Wrapper to decode a request packet

        :param message: The raw modbus request packet
        :return: The decoded modbus message or None if error
        """
        try:
            return self._helper(message)
        except ModbusException as er:
            _logger.warning("Unable to decode request %s" % er)
        return None

    def lookupPduClass(self, function_code):
        """ Use `function_code` to determine the class of the PDU.

        :param function_code: The function code specified in a frame.
        :returns: The class of the PDU that has a matching `function_code`.
        """
        return self.__lookup.get(function_code, ExceptionResponse)

    def _helper(self, data):
        """
        This factory is used to generate the correct request object
        from a valid request packet. This decodes from a list of the
        currently implemented request types.

        :param data: The request packet to decode
        :returns: The decoded request or illegal function request object
        """
        function_code = byte2int(data[1])
        request = self.__lookup.get(function_code, lambda: None)()
        if not request:
            _logger.debug("Factory Request[%d]" % function_code)
            request = IllegalFunctionRequest(function_code)
        else:
            fc_string = "%s: %s" % (
                str(self.__lookup[function_code]).split('.')[-1].rstrip(
                    "'>"),
                function_code
            )
            _logger.debug("Factory Request[%s]" % fc_string)

        content = data[3:len(data)-1]
        _logger.debug("***** decoded data below :")
        _logger.debug(content)
        # request.decode(content)

        if hasattr(request, 'sub_function_code'):
            lookup = self.__sub_lookup.get(request.function_code, {})
            subtype = lookup.get(request.sub_function_code, None)
            if subtype: request.__class__ = subtype

        #return request
        return content

    def register(self, function=None):
        """
        Registers a function and sub function class with the decoder
        :param function: Custom function class to register
        :return:
        """
        if function and not issubclass(function, ModbusRequest):
            raise MessageRegisterException("'{}' is Not a valid Modbus Message"
                                           ". Class needs to be derived from "
                                           "`pymodbus.pdu.ModbusRequest` "
                                           "".format(
                function.__class__.__name__
            ))
        self.__lookup[function.function_code] = function
        if hasattr(function, "sub_function_code"):
            if function.function_code not in self.__sub_lookup:
                self.__sub_lookup[function.function_code] = dict()
            self.__sub_lookup[function.function_code][
                function.sub_function_code] = function

